package hasthi.actuators;

import hasthi.actuators.actions.CommandGenerator;
import hasthi.actuators.impl.HostAgentBasedHostProxy;
import hasthi.common.HasthiException;
import hasthi.common.Utils;
import hasthi.common.HasthiException.FaultCode;
import hasthi.common.constants.HasthiConstants;
import hasthi.common.constants.WSDMConstants;
import hasthi.config.DeploymentConfig;
import hasthi.config.ResourceProfile;
import hasthi.container.ContainerUtils;
import hasthi.container.SimpleMessageContext;
import hasthi.manager.ExecuteCommandDocument;
import hasthi.manager.ExecuteCommandResponseDocument;
import hasthi.manager.ExecuteCommandDocument.ExecuteCommand;
import hasthi.metamodel.resource.ManagedResource;
import hasthi.metamodel.resource.ManagedService;
import hasthi.tools.FailureDetectionTools;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;

import javax.xml.namespace.QName;

public class CreateServiceAction extends ManagementAction {

    // TODO this is to support getting location of other resources from a one
    // service
    // private static Pattern p2 =
    // Pattern.compile(Pattern.quote("${Instance."+"(.*)\\}"));

    private QName serviceType;
    private ResourceProfile resourceProfile;
    private ManagedResource oldResource;
    private String group;
    private String targetHost;
    private String newServiceUrl;
    private List<ManagedResource> hostAgents;

    public CreateServiceAction(ResourceProfile resourceConfig) throws HasthiException {
        if (resourceConfig == null) {
            throw new HasthiException("Resource profile must not be Null", FaultCode.ConfigError);
        }
        this.resourceProfile = resourceConfig;
        if (resourceConfig != null) {
            this.serviceType = resourceProfile.getName();
        }
    }

    public CreateServiceAction(String serviceType, String group) throws HasthiException {
        this.serviceType = QName.valueOf(serviceType);
        this.group = group;

    }

    public CreateServiceAction(ManagedResource oldResource) throws HasthiException {
        this(oldResource.getType(), null);
        this.oldResource = oldResource;
        this.serviceType = QName.valueOf(oldResource.getType());
    }

    public String getActionID() {
        return this.getClass().getName() + "|" + serviceType;
    }

    public void execute() throws HasthiException {
        try {
            if (resourceProfile == null) {
                SystemConfig systemConfig = actionContext.getSystemConfig();
                this.resourceProfile = systemConfig.getResourceProfile(serviceType);
                if (resourceProfile == null) {
                    throw new HasthiException("Resource profile for " + serviceType + " not found",
                            FaultCode.ConfigError);
                }
            }

            DeploymentConfig resourceConfig = resourceProfile.getDeployment();

            if (resourceConfig == null || resourceConfig.getInstallDir() == null
                    || (resourceConfig.getStartupCommand() == null && resourceConfig.getContainer() == null) ) {
                throw new HasthiException("Resource " + resourceProfile.getName()
                        + " is not configured for restart", FaultCode.ConfigError);
            }

            RemoteHostProxy remoteSystemProxy;
            // We try to use a host agent, and we randomly start from some point in
            // the host agent array
            // and try to find a matching host
            ManagedResource deployHost = null;

            if (targetHost == null) {
                //No Target Host Specified
                //If there is a old resource, we first try to create the service in the same host
                deployHost = findDeployHostFromOldResource();
                //We will pick a Host from Resource Profile;            
                if(deployHost == null){
                    deployHost = pickAHost(resourceConfig);    
                }
            } else {
                deployHost = actionContext.getResourceModel().getResource(new QName(targetHost));
            }

            if (deployHost != null) {
                targetHost = deployHost.getName();

                if (HasthiConstants.Actions.CONTAINER_TOMCAT.equals(resourceConfig.getContainer())) {
                    ExecuteCommandDocument commandDocument = ExecuteCommandDocument.Factory
                            .newInstance();
                    ExecuteCommand executeCommand = commandDocument.addNewExecuteCommand();
                    executeCommand.setWorkdir(resourceConfig.getInstallDir());
                    executeCommand.setCmd(HasthiConstants.Actions.COMMAND_TOMCAT_START);
                    ExecuteCommandResponseDocument responseDocument = (ExecuteCommandResponseDocument) actionContext
                            .getUnicastContainer().sendReceive(
                                    new SimpleMessageContext(commandDocument, deployHost
                                            .getManagerAgentURL(),
                                            HasthiConstants.EXECUATE_COMMAND_ACTION));
                    String[] responseParams = responseDocument.getExecuteCommandResponse()
                            .getResponseParamsArray();
                    if (responseParams != null && responseParams.length > 0) {
                        newServiceUrl = responseParams[0];
                    }
                } else {
                    String remotescriptfile = CommandGenerator.insertVariables(actionContext,
                            oldResource, resourceConfig.getStartupCommand(), new String[][] { {
                                    WSDMConstants.GROUP_PARAM.getLocalPart(), group } });
                    remoteSystemProxy = new HostAgentBasedHostProxy(actionContext.getBaseContext(),
                            deployHost.getManagerAgentURL(), actionContext.getUnicastContainer());
                    String[] results = remoteSystemProxy.runCommand(deployHost.getName(),
                            remotescriptfile, null, resourceConfig.getInstallDir(), false, "");
                    if (results != null && results.length > 0) {
                        newServiceUrl = results[0];
                    }

                    String serviceUrl = null;
                    if (resourceConfig.getServiceEndpoint() != null) {
                        serviceUrl = resourceConfig.getServiceEndpoint().replaceAll("\\$HOST",
                                targetHost);
                    }else if(oldResource != null){
                        URL url = new URL(oldResource.getManagerAgentURL());
                        serviceUrl = resourceConfig.getServiceEndpoint().replaceAll(url.getHost(),
                                targetHost);
                    }
                    
                    if(serviceUrl != null){
                        int repeatcount = 0;
                        while (repeatcount < 3) {
                            try {
                                Thread.sleep(5000);
                                ContainerUtils.doUrlGet(actionContext.getBaseContext(), serviceUrl);
                                break;
                            } catch (Throwable e) {
                                repeatcount++;
                                if (repeatcount >= 3) {
                                    throw new HasthiException("Failed to start service "+resourceProfile.getName() + " in the host " 
                                            + targetHost ,e, FaultCode.ConfigError);
                                }
                            }
                        }
                        newServiceUrl = serviceUrl;
                    }
                }
                return;
                // if(oldResource != null){
                // ManagementActionUtils.removeResource(actionContext,
                // oldResource);
                // }
            } else {
                log.error(resourceProfile.getName() + " does not have a matching host "
                        + resourceConfig.getHostNamePattern());
            }

            throw new HasthiException(resourceProfile.getName() + " does not have a matching host "
                    + resourceConfig.getHostNamePattern(), FaultCode.ConfigError);
        } catch (MalformedURLException e) {
            throw new HasthiException(e, FaultCode.InternalServiceError);
        }

        /*
         * // We use Grid based deployment, maybe later archive based deployment //
         * can be done using host agent as well if (targetHost == null) {
         * String[] deployhosts =
         * resourceProfile.getDeployment().getHostNameArray(); if
         * (deployhosts.length == 0) { throw new HasthiException("Can not find a
         * sutable host to create the service " + resourceProfile.getName(),
         * FaultCode.ConfigError); } targetHost = deployhosts[0]; }
         * 
         * remoteSystemProxy = new
         * GridbasedHostProxy(actionContext.getBaseContext()); String
         * remoteWorkDir = resourceProfile.getDeployment().getInstallDir();
         * String remotescriptfile;
         * 
         * if (resourceConfig.getDistArchive() != null) { throw new
         * UnsupportedOperationException(); // String url =
         * resourceConfig.getDistArchive(); // String execuatable =
         * resourceConfig.getStartupCommand(); // String zipfileName =
         * url.substring(url.lastIndexOf("/") + 1); // String zipfileNamePart =
         * zipfileName.replaceAll("\\.zip", ""); // String script =
         * "/usr/bin/wget " + url + "\n" + "mkdir " + // zipfileNamePart + "\n" // +
         * "unzip " + zipfileName + " -d " + zipfileNamePart + "\n" // + "export
         * JAVA_HOME=" + hostConfig.getJavaHome() + "\n" + "cd " // +
         * zipfileNamePart + "\n" + "chmod 755 *.sh\n" + "source // ~/.bashrc\n" // +
         * "nohup ./" + execuatable + "> " + serviceName + ".log &\n"; // File
         * tempscript = File.createTempFile(zipfileNamePart, ".sh"); //
         * FileOutputStream tempscriptStream = new //
         * FileOutputStream(tempscript); //
         * tempscriptStream.write(script.getBytes()); //
         * tempscriptStream.close(); // // remoteSystemProxy.copyToDir(new
         * URI("gsiftp://" // + InetAddress.getLocalHost().getHostName() + "/" // +
         * tempscript.getAbsolutePath()), new URI("gsiftp://" + hostName + //
         * "/" // + remoteWorkDir)); // remotescriptfile = remoteWorkDir + "/" +
         * tempscript.getName(); // remoteSystemProxy.runCommand(hostName,
         * "/bin/chmod", new String[] // { "755", // remotescriptfile },
         * remoteWorkDir, false, ""); // log.info(remotescriptfile); } else if
         * (resourceConfig.getInstallDir() != null) { remotescriptfile =
         * resourceConfig.getInstallDir() + "/" +
         * resourceConfig.getStartupCommand(); } else { // handle war file
         * deployment throw new UnsupportedOperationException(); } String[]
         * results = remoteSystemProxy.runCommand(targetHost, remotescriptfile,
         * null, remoteWorkDir, false, resourceConfig.getVerificationStr());
         * if(results != null && results.length > 0){ newServiceUrl =
         * results[0]; }
         */
    }

    private ManagedResource pickAHost(DeploymentConfig resourceConfig) throws HasthiException {
        ManagedResource deployHost = null;
        String[] hostNameArray = resourceConfig.getHostNameArray();            
        //this will let us run actions without a rule engine
        if (hostAgents == null && actionContext.getRuleEngine() != null) {
            hostAgents = actionContext.getRuleEngine().queryManagedResources("FindHostAgents",
                    new String[0]);
        }
        if (hostAgents != null && hostAgents.size() > 0) {
            int length = hostAgents.size();
            int start = Math.abs((int) System.currentTimeMillis() % length);
            int end = start + length;

            if (hostNameArray != null && hostNameArray.length > 0) {
                for (String hostName : hostNameArray) {
                    for (int i = start; i < end; i++) {
                        ManagedResource host = hostAgents.get(Math.abs(i % length));
                        if (host.getName().equals(hostName) 
                                && FailureDetectionTools.isSocketUp(host.getManagerAgentURL())) {
                            deployHost = host;
                            break;
                        }
                    }
                }
            } else if (resourceConfig.getHostNamePattern() != null) {
                for (int i = start; i < end; i++) {
                    ManagedResource host = hostAgents.get(Math.abs(i % length));
                    if (Utils.isMatches(host.getName(), resourceConfig.getHostNamePattern())
                            && FailureDetectionTools.isSocketUp(host.getManagerAgentURL())) {
                        deployHost = host;
                        break;
                    }
                }
            }
        }
        return deployHost;
    }

    private ManagedResource findDeployHostFromOldResource()
            throws HasthiException {
        ManagedResource deployHost = null;
        if(oldResource != null && oldResource instanceof ManagedService){
            String oldHost = ((ManagedService)oldResource).getHost();
            ManagedResource resource = actionContext.getResourceModel().getResource(new QName(oldHost));
            if(resource != null && FailureDetectionTools.isSocketUp(resource.getManagerAgentURL())){
                //ok old host is up, so we create the resource in the same place
                deployHost = resource;
            }
        }
        return deployHost;
    }

    public String getTargetHost() {
        return targetHost;
    }

    public void setTargetHost(String targetHost) {
        this.targetHost = targetHost;
    }

    public String getNewServiceUrl() {
        return newServiceUrl;
    }

    public void setHostAgents(List<ManagedResource> hostAgents) {
        this.hostAgents = hostAgents;
    }

    public QName getServiceType() {
        return serviceType;
    }

    public void setNewServiceUrl(String newServiceUrl) {
        this.newServiceUrl = newServiceUrl;
    }
}
